"""数据仓库：负责从数据库加载并组织设备与分类信息。"""

from __future__ import annotations

import json
from collections import defaultdict
from typing import Dict, Iterable, List, Optional

from .models import Device, DeviceAttribute, TypeNode


class DeviceRepository:
    """面向 UI 与业务服务提供只读数据接口。"""

    def __init__(self, raw_items: List[dict]) -> None:
        """
        初始化设备仓库。
        
        Args:
            raw_items: 原始数据项列表（从数据库加载）
        """
        self._type_nodes: Dict[int, TypeNode] = {}
        self._root_nodes: List[TypeNode] = []
        self._devices: Dict[int, Device] = {}
        self._type_to_devices: Dict[int, List[int]] = defaultdict(list)

        self._load_from_raw_items(raw_items)
        self._build_tree()

    @classmethod
    def from_database(cls, db_connector) -> "DeviceRepository":
        """
        从数据库创建设备仓库实例。
        
        Args:
            db_connector: 数据库连接器实例（DeviceDataManager）
            
        Returns:
            DeviceRepository 实例
        """
        raw_items = db_connector.process_device_data()
        return cls(raw_items=raw_items)

    def _load_from_raw_items(self, raw_items: Iterable[dict]) -> None:
        """从原始数据项列表加载数据（用于数据库或文件数据）。"""
        import logging
        logger = logging.getLogger(__name__)
        
        item_count = 0
        for raw in raw_items:
            item_count += 1
            try:
                # 检查数据结构
                device_id = raw.get("id")
                iot_device_type_node = raw.get("iot_device_type_node")
                iom_id_list = raw.get("iom_id", [])
                
                if not iot_device_type_node:
                    logger.warning(f"设备 {device_id} 缺少 iot_device_type_node 字段，跳过")
                    continue
                
                if not isinstance(iom_id_list, list):
                    logger.warning(f"设备 {device_id} 的 iom_id 不是列表类型，跳过")
                    continue
                
                logger.debug(f"处理设备 {device_id}：类型节点={iot_device_type_node.get('id')}, 物模型数量={len(iom_id_list)}")
                
                node = self._parse_type_node(iot_device_type_node)
                device = self._parse_device(raw, node)

                # 只有当节点不存在时才添加（避免重复）
                if node.id not in self._type_nodes:
                    self._type_nodes[node.id] = node
                
                # 设备可能已存在（如果同一设备有多条物模型记录）
                if device.id not in self._devices:
                    self._devices[device.id] = device
                
                # 建立类型到设备的映射（避免重复）
                if device.id not in self._type_to_devices[node.id]:
                    self._type_to_devices[node.id].append(device.id)
            except Exception as exc:  # noqa: BLE001
                logger.exception(f"处理数据项时发生错误（跳过该项）：{exc}，原始数据ID：{raw.get('id', 'unknown')}")
                continue
        
        logger.info(f"数据加载完成：处理了 {item_count} 条记录，创建了 {len(self._type_nodes)} 个分类节点，{len(self._devices)} 个设备")

    def _build_tree(self) -> None:
        """构建分类树的父子引用关系。"""
        import logging
        logger = logging.getLogger(__name__)
        
        # 先清空根节点列表，避免重复添加
        self._root_nodes.clear()
        
        # 第一遍：建立父子关系
        missing_parents = set()
        for node in self._type_nodes.values():
            if node.parent_id is not None:
                if node.parent_id in self._type_nodes:
                    parent = self._type_nodes[node.parent_id]
                    parent.add_child(node)
                else:
                    # 记录缺失的父节点（可能是数据不完整，但仍将其作为根节点）
                    missing_parents.add(node.parent_id)
                    logger.debug(f"节点 {node.id} 的父节点 {node.parent_id} 不存在，将其作为根节点")
                    self._root_nodes.append(node)
            else:
                # parent_id 为 None，是根节点
                self._root_nodes.append(node)
        
        if missing_parents:
            logger.warning(f"发现 {len(missing_parents)} 个缺失的父节点：{missing_parents}，相关节点已作为根节点处理")
        
        # 为了在 UI 中保持稳定顺序，按名称排序
        for node in self._type_nodes.values():
            node.children.sort(key=lambda n: (n.sort_key(), n.name))

        self._root_nodes.sort(key=lambda n: (n.sort_key(), n.name))
        
        logger.info(f"树构建完成：根节点数量={len(self._root_nodes)}, 总节点数量={len(self._type_nodes)}")

    def get_root_nodes(self) -> List[TypeNode]:
        return list(self._root_nodes)

    def get_children(self, parent_id: int) -> List[TypeNode]:
        node = self._type_nodes.get(parent_id)
        return list(node.children) if node else []

    def get_devices_by_type(self, type_id: int) -> List[Device]:
        device_ids = self._type_to_devices.get(type_id, [])
        return [self._devices[d_id] for d_id in device_ids]

    def get_device(self, device_id: int) -> Optional[Device]:
        return self._devices.get(device_id)

    def get_all_type_nodes(self) -> List[TypeNode]:
        """返回一个包含所有分类节点的列表副本，用于界面层进行自定义分组。"""
        return list(self._type_nodes.values())

    # ---------------- private helpers ---------------- #

    def _parse_type_node(self, raw_node: Optional[dict]) -> TypeNode:
        if not raw_node:
            raise ValueError("数据缺少 iot_device_type_node 字段")

        node_id = int(raw_node.get("id"))
        parent_id = raw_node.get("parent_id")
        parent_id = int(parent_id) if parent_id is not None else None

        type_node = self._type_nodes.get(node_id)
        if type_node:
            return type_node

        return TypeNode(
            id=node_id,
            name=raw_node.get("name", f"未命名分类-{node_id}"),
            parent_id=parent_id,
            product_key=raw_node.get("product_key"),
            sort_number=self._coerce_int(raw_node.get("sort_number")),
        )

    def _parse_device(self, raw: dict, node: TypeNode) -> Device:
        device_id = int(raw.get("id"))
        nickname = raw.get("nickname") or raw.get("name") or f"设备-{device_id}"
        device_properties = self._safe_json(raw.get("device_properties"))

        product_key = raw.get("iot_device_type_node", {}).get("product_key") or device_properties.get(
            "deviceInstance", {}
        ).get("productKey") or ""

        device_sn = raw.get("device_s_n") or device_properties.get("deviceInstance", {}).get("deviceSN") or ""
        device_secret = raw.get("device_secret") or device_properties.get("deviceInstance", {}).get("deviceSecret") or ""

        attributes = [self._parse_attribute(attr) for attr in raw.get("iom_id", [])]

        return Device(
            id=device_id,
            nickname=nickname,
            device_name=raw.get("device_name", nickname),
            product_key=product_key,
            device_sn=device_sn,
            device_secret=device_secret,
            type_node_id=node.id,
            type_node_name=node.name,
            type_node_parent_id=node.parent_id,
            attributes=attributes,
            raw_payload=raw,
        )

    @staticmethod
    def _parse_attribute(raw_attr: dict) -> DeviceAttribute:
        default_value = raw_attr.get("properties_default_value")
        if isinstance(default_value, str) and default_value.strip() == "":
            default_value = None
        optional_values_raw = DeviceRepository._safe_json(raw_attr.get("properties_optional"))
        if not isinstance(optional_values_raw, dict):
            optional_values_raw = {}
        optional_values = {str(key): str(value) for key, value in optional_values_raw.items()}

        return DeviceAttribute(
            id=int(raw_attr.get("id")),
            nickname=raw_attr.get("nickname", raw_attr.get("resource_name", "未命名属性")),
            resource_name=raw_attr.get("resource_name", ""),
            point_type=raw_attr.get("point_type", ""),
            value_type=raw_attr.get("properties_value_type", ""),
            read_write=raw_attr.get("properties_read_write", ""),
            units=raw_attr.get("properties_units"),
            description=raw_attr.get("description"),
            default_value=default_value,
            optional_values=optional_values,
        )

    @staticmethod
    def _safe_json(raw_value):
        if isinstance(raw_value, dict):
            return raw_value
        if isinstance(raw_value, str) and raw_value:
            try:
                return json.loads(raw_value)
            except json.JSONDecodeError:
                return {}
        return {}

    @staticmethod
    def _coerce_int(value) -> Optional[int]:
        if value is None or value == "":
            return None
        try:
            return int(value)
        except (TypeError, ValueError):
            return None

